﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Reflection;
using System.Collections;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.Utilities;

namespace WorkflowTaskReplicator
{
    public class WorkflowTaskReplicatorItemEventReceiver : SPItemEventReceiver
    {
        /// <summary>
        /// Synchronous Before event that occurs when a new item is added to its containing object.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemAdding(SPItemEventProperties properties)
        {
            //Set fixed title
            properties.AfterProperties[Helper.Task_Title] = "Workflow Task Replicator";

            base.ItemAdding(properties);
        }

        /// <summary>
        /// Asynchronous After event that occurs after a new item has been added to its containing object.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemAdded(SPItemEventProperties properties)
        {
            EventFiringEnabled = false;

            //Set list item permissons for current item
            Helper.SetListItemPermissions(properties.ListItem, null, null, null, null);

            //Try to parse the requested content type id
            SPContentTypeId ctId = SPContentTypeId.Empty;

            try
            {
                ctId = new SPContentTypeId((string)properties.ListItem[Helper.WorkflowTaskReplicator_ContentType]);
            }
            catch (ArgumentException)
            {

            }

            if (ctId != SPContentTypeId.Empty)
            {
                //Check if task list implements the requested content type
                SPContentTypeId listCtId = properties.List.ContentTypes.BestMatch(ctId);

                if (listCtId != null && !listCtId.IsChildOf(Helper.WorkflowTaskReplicatorContentTypeId))
                {
                    if (listCtId.IsChildOf(ctId))
                    {
                        //Check if our item event receiver is already attached to the list
                        Helper.AddListItemEventReceivers(properties.List);

                        //Make sure the user value columns have been set
                        Helper.SetUserValueColumns(properties.ListItem);

                        SPFieldUserValueCollection assignees = (SPFieldUserValueCollection)properties.ListItem[Helper.WorkflowTaskReplicator_AssignedTo];
                        List<SPUser> contributors = Helper.ConvertSPFieldUserValueCollection((SPFieldUserValueCollection)properties.ListItem[Helper.WorkflowTaskReplicator_Contributors]);
                        List<SPUser> owners = Helper.ConvertSPFieldUserValueCollection((SPFieldUserValueCollection)properties.ListItem[Helper.WorkflowTaskReplicator_Owners]);
                        List<SPUser> readers = Helper.ConvertSPFieldUserValueCollection((SPFieldUserValueCollection)properties.ListItem[Helper.WorkflowTaskReplicator_Readers]);
                        Dictionary<string, string> subTaskMetaData = Helper.DeserializeSubTaskMetaData((string)properties.ListItem[Helper.WorkflowTaskReplicator_SubTaskMetaData]);
                        Dictionary<Guid, bool> subTasks = new Dictionary<Guid, bool>();

                        if (assignees != null)
                        {
                            //Create task for each assignee
                            foreach (SPFieldUserValue assignee in assignees)
                            {
                                SPListItem newTask = properties.List.Items.Add();

                                newTask[SPBuiltInFieldId.ContentTypeId] = listCtId;
                                newTask[SPBuiltInFieldId.AssignedTo] = assignee;

                                //To make sure the tasks are visible on the workflow status page and that they're deleted when the workflow is terminated
                                newTask[SPBuiltInFieldId.WorkflowInstanceID] = properties.ListItem[SPBuiltInFieldId.WorkflowInstanceID];

                                //Copy all metadata to the new task
                                foreach (KeyValuePair<string, string> kvp in subTaskMetaData)
                                {
                                    //Ignore fields that have been previously set by our code
                                    if (kvp.Key != "ContentTypeId" && kvp.Key != "AssignedTo" && kvp.Key != "WorkflowInstanceID")
                                    {
                                        try
                                        {
                                            newTask[kvp.Key] = kvp.Value;
                                        }
                                        catch
                                        {
                                            Helper.Log(String.Format("Error setting sub task meta data. Content type: '{0}', column: '{1}', value: '{2}'.", listCtId, kvp.Key, kvp.Value));
                                        }
                                    }
                                }

                                newTask.Update();

                                Helper.Log(String.Format("Task with content type '{0}' created for user '{1}'.", listCtId, assignee.User.LoginName));

                                Helper.SetListItemPermissions(newTask, assignee.User, contributors, owners, readers);

                                //Add sub task to list
                                subTasks.Add(newTask.UniqueId, false);
                            }

                            //Add subtask info to replicator task
                            if (subTasks.Count > 0)
                            {
                                properties.ListItem[Helper.WorkflowTaskReplicator_SubTasks] = Helper.SerializeSubTasks(subTasks);
                                properties.ListItem.UpdateOverwriteVersion();
                            }
                        }
                        else
                            Helper.Log("No users were assigned to the task.");
                    }
                    else
                        Helper.Log(String.Format("Task list doesn't implement content type '{0}'.", listCtId));
                }
                else
                    Helper.Log("Requested content type id equals WorkflowTaskReplicator content type id (causes infinite looping).");
            }
            else
                Helper.Log("Error parsing content type id!");

            EventFiringEnabled = true;

            base.ItemAdded(properties);
        }

        /// <summary>
        /// Synchronous Before event that occurs when an existing item is changed, for example, when the user changes data in one or more fields.
        /// </summary>
        /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> object that represents properties of the event handler.</param>
        public override void ItemUpdating(SPItemEventProperties properties)
        {
            //Check if all sub tasks have been completed
            Dictionary<Guid, bool> tasks;
            int completedCount = 0;

            tasks = Helper.DeserializeSubTasks((string)properties.AfterProperties[Helper.WorkflowTaskReplicator_SubTasks]);

            if (tasks.Count > 0)
            {
                foreach (bool taskComplete in tasks.Values)
                {
                    if (taskComplete)
                        completedCount++;
                }

                //Mark current task as completed or caclulate percentage done
                if (tasks.Count == completedCount || !(bool)properties.ListItem[Helper.WorkflowTaskReplicator_AllSubTasksMustBeCompleted])
                {
                    properties.AfterProperties[Helper.Task_PercentComplete] = "1";
                    properties.AfterProperties[Helper.Task_Status] = SPUtility.GetLocalizedString("$Resources:core,Tasks_Completed;", "", properties.Web.Language);
                }
                else
                {
                    properties.AfterProperties[Helper.Task_PercentComplete] = Math.Round((double)completedCount / tasks.Count, 2).ToString();
                    properties.AfterProperties[Helper.Task_Status] = SPUtility.GetLocalizedString("$Resources:core,Tasks_InProgress;", "", properties.Web.Language);
                }
            }
            else
                Helper.Log(String.Concat("No sub tasks defined for item '{0}'.", properties.ListItem.UniqueId));

            base.ItemUpdating(properties);
        }

        /// <summary>
        /// Asynchronous After event that occurs after an existing item is changed, for example, when the user changes data in one or more fields.
        /// </summary>
        /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPItemEventProperties"/> object that represents properties of the event handler.</param>
        public override void ItemUpdated(SPItemEventProperties properties)
        {
            base.ItemUpdated(properties);
        }

        /// <summary>
        /// Synchronous Before event that occurs before an existing item is completely deleted.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemDeleting(SPItemEventProperties properties)
        {
            //Delete all referenced sub tasks
            Dictionary<Guid, bool> tasks;
            SPListItem subTask;

            tasks = Helper.DeserializeSubTasks((string)properties.ListItem[Helper.WorkflowTaskReplicator_SubTasks]);

            foreach (Guid subtaskId in tasks.Keys)
            {
                try
                {
                    subTask = properties.List.Items[subtaskId];
                }
                catch (ArgumentException)
                {
                    subTask = null;
                }

                if(subTask != null)
                    subTask.Delete();
            }

            base.ItemDeleting(properties);
        }

        /// <summary>
        /// Asynchronous After event that occurs after an existing item is completely deleted.
        /// </summary>
        /// <param name="properties"></param>
        public override void ItemDeleted(SPItemEventProperties properties)
        {
            base.ItemDeleted(properties);
        }
    }
}